package ru.megazlo.basemvc.controllers.base;

import com.google.gson.Gson;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.http.MediaType;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

public class BaseController {

	protected final String CONTENT = "#content";
	protected final String DIALOG = "#dialogsContainer";
	protected final String ALERT = "#alertContainer";

	private static final String suffix = "Controller";

	private boolean useAjax = true;
	private HttpServletRequest request;
	private InternalResourceViewResolver viewResolver;
	private Validator validator;
	private ResourceBundleMessageSource messageSource;
	private Gson gs = new Gson();

	public Object redirect(String redirectUrl, HttpServletResponse response) {
		if (isAjax()) {
			redirectAjax(redirectUrl, response);
			response.setContentType(MediaType.APPLICATION_JSON.toString());
			return null;
		}
		return redirectUrl;
	}

	public Object getResult(HttpServletResponse response) {
		return getResult(new HashMap<>(), response);
	}

	public Object getResult(String modelName, Object model, HttpServletResponse response) {
		Map<String, Object> md = new HashMap<>();
		md.put(modelName, model);
		return getResult(md, response);
	}

	public Object getResult(Map<String, Object> model, HttpServletResponse response) {
		ModelAndView mv = new ModelAndView();
		for (String key : model.keySet()) {
			mv.addObject(key, model.get(key));
		}
		return getResult(mv, response);
	}

	public Object getResult(ModelAndView mv, HttpServletResponse response) {
		MetaAction meta = getActionAnnotation();
		validateModel(mv);
		mv.getModel().put("title", meta.getTitle());
		mv.setViewName(meta.getView());
		if (isAjax()) {
			JsonData responseData = getAjaxResponse(meta, mv, response);
			writeAjaxContent(responseData, response);
			response.setContentType(MediaType.APPLICATION_JSON.toString());
			return null;
		}
		return mv;
	}

	protected void validateModel(ModelAndView mv) {
		Map<String, Object> ers = new HashMap<>();
		if (isGet()) {
			return;
		}
		for (String key : mv.getModel().keySet()) {
			BindingResult bnd = new BeanPropertyBindingResult(mv.getModel().get(key), key);
			bnd.getAllErrors();
			validator.validate(mv.getModel().get(key), bnd);
			if (bnd.hasErrors()) {
				ers.put(BindingResult.class.getName() + '.' + key, bnd);
			}
		}
		for (String key : ers.keySet()) {
			mv.getModel().put(key, ers.get(key));
		}
	}

	protected void redirectAjax(String redirectUrl, HttpServletResponse response) {
		JsonData responseData = new JsonData();
		responseData.setRedirect(request.getContextPath() + redirectUrl);
		writeAjaxContent(responseData, response);
	}

	private void writeAjaxContent(JsonData responseData, HttpServletResponse response) {
		try {
			response.setCharacterEncoding("utf-8");
			response.setContentType("text/json");
			response.setStatus(200);
			response.getWriter().println(gs.toJson(responseData));
			response.getWriter().flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	protected JsonData getAjaxResponse(MetaAction meta, ModelAndView mv, HttpServletResponse response) {
		String renderedView = renderAjaxView(mv, response);
		JsonData rez = new JsonData();
		rez.setTitle(meta.getTitle());
		Replacer repl = new Replacer();
		repl.setData(renderedView);
		repl.setSelect(meta.getSelector());
		rez.getRepl().add(repl);
		return rez;
	}

	protected MetaAction getActionAnnotation() {
		String methName = Arrays.asList(Thread.currentThread().getStackTrace()).stream().filter(x -> x.getClassName().equals(getClass().getCanonicalName())).findFirst().get().getMethodName();
		Method m = Arrays.asList(getClass().getDeclaredMethods()).stream().filter(mt -> mt.getName().equals(methName)).findFirst().get();
		if (m == null) {
			throw new RuntimeException("Не найден экшен контроллера " + getClass());
		}
		Action cmn = m.getAnnotation(Action.class);
		if (cmn == null) {
			throw new RuntimeException("Не указана аннотация к методу " + m.getName());
		}
		MetaAction rez = new MetaAction();
		if (!getClass().getSimpleName().endsWith(suffix)) {
			throw new RuntimeException("Название класса контроллера должно оканчиваться на " + suffix);
		}
		rez.setController(getClass().getSimpleName().replace(suffix, ""));
		rez.setAction(m.getName());
		rez.setSelector(cmn.elem());
		final String contr = rez.getController().toLowerCase();
		rez.setView("".equals(cmn.view()) ? contr + '/' + rez.getAction() : cmn.view());
		final String title = messageSource.getMessage(contr + '.' + rez.getAction(), null, request.getLocale());
		if (title == null) {
			throw new RuntimeException("Не указан заголовок " + rez.getController().toLowerCase() + '.' + rez.getAction().toLowerCase());
		}
		rez.setTitle(title);
		return rez;
	}

	protected String renderAjaxView(ModelAndView mv, HttpServletResponse response) {
		try {
			View view = viewResolver.resolveViewName(mv.getViewName(), request.getLocale());
			try (AjaxHttpServletResponse ajResp = new AjaxHttpServletResponse(response)) {
				view.render(mv.getModel(), request, useAjax ? ajResp : response);
				return ajResp.getAjaxResult();
			}
		} catch (Exception ignored) {
			throw new RuntimeException(ignored);
		}
	}

	public boolean isAjaxUpload() {
		return request.getParameter("ajaxUpload") != null;
	}

	protected boolean isAjax() {
		return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
	}

	protected boolean isPost() {
		return "POST".equalsIgnoreCase(request.getMethod());
	}

	protected boolean isGet() {
		return "GET".equalsIgnoreCase(request.getMethod());
	}

	@Autowired
	public void setMessageSource(ResourceBundleMessageSource messageSource) {
		this.messageSource = messageSource;
	}

	@Autowired
	public void setRequest(HttpServletRequest request) {
		this.request = request;
	}

	@Autowired
	@Qualifier("customViewResolver")
	public void setViewResolver(InternalResourceViewResolver viewResolver) {
		this.viewResolver = viewResolver;
	}

	@Autowired
	@Qualifier("localValidator")
	public void setValidator(Validator validator) {
		this.validator = validator;
	}

	public void setUseAjax(boolean useAjax) {
		this.useAjax = useAjax;
	}
}
